/*
   \file  train.cc
   \brief Training action for different libgist algorithms.

   This file defines the static data members and non-inline member
   functions of the train class. See the header file for more info.
*/

/*
   This file is part of libgist.

   libgist is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2 of the License, or (at your
   option) any later version.

   libgist is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
   for more details.

   You should have received a copy of the GNU General Public License
   along with libgist; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/

/*
   REVISION HISTORY

   $HeadURL: http://libgist.googlecode.com/svn/trunk/drv/train.cc $
   $Id: train.cc 73 2011-09-29 08:38:57Z libgist@gmail.com $
*/

//------------------------------ HEADERS --------------------------------

// gistdrv
#include "train.hh"
#include "config.hh"
#include "file_utils.hh"
#include "math_utils.hh"

// libgist
#include "threaded_image_set_processor.hh"

// Boost
#include <boost/lambda/if.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

// Standard C++
#include <iostream>
#include <map>
#include <vector>
#include <string>
#include <stdexcept>

//----------------------- NAMESPACE DEFINITION --------------------------

namespace gistdrv {

//------------------ STATIC DATA MEMBERS AND GLOBALS --------------------

// Factory setup
static std::string ACTION_NAME("train") ;
train::my_factory train::register_me(ACTION_NAME) ;

//-------------------- INITIALIZATION AND CLEAN-UP ----------------------

train::train(algorithm_tester* algo)
   : action(algo)
{}

train::~train(){}

//----------------------------- TRAINING --------------------------------

// DEVNOTE: To those unfamiliar with this style (original author included
// when learning Boost.Lambda), the code to load the training images in
// multiple threads may warrant some explanation...
//
// It works by creating a temporary threaded_image_set_processor object,
// passing it the training_images map of lists, and then uses the
// num_threads configuration setting to run multiple threads to collect
// the training data for the chosen algorithm.
//
// As its second argument, threaded_image_set_processor::run() expects a
// callable that will take a string and whatever is held in the
// image_list type (another string in this case). Here, we use
// boost::lambda::if_then_else to check if the image file (placeholder
// _2; see run method's code) exists and, if so, to pass the image
// category (_1) and file name (_2) to the algorithm tester's
// collect_training_data method. If the file does not exist, we will
// print an error message to that effect.
//
// If the loading process throws an exception, that thread will exit with
// a suitable error message printed by the third argument to the run
// method.
//
// Once run is done, the temporary threaded_image_set_processor object
// will go out of scope and the threads it created will be deleted.
void train::exec()
{
   using boost::lambda::_1 ;
   using boost::lambda::_2 ;
   using boost::lambda::bind ;
   using boost::lambda::if_then_else ;
   using boost::lambda::constant ;

   // From config system, obtain list of training images organized into
   // their respective categories. The keys of this map are the category
   // labels and the values are the lists of images for each category.
   typedef std::vector<std::string> image_list ;
   std::map<std::string, image_list> training_images =
      config::get_map_of_lists<std::string, std::string>("training_images") ;

   // Load the training images using multiple threads and pass each of
   // them to the algorithm. See comment above function for explanation.
   gist::threaded_image_set_processor<image_list>(training_images).
      run(round(config::get<float>("num_threads")),
          if_then_else(bind(exists, _2),
                       bind(&algorithm_tester::collect_training_data,
                            m_algo, _1, _2),
                       std::cerr << constant("gist: ") << ACTION_NAME << ": "
                                 << _2 << ": no such file\n"),
          std::cerr << constant("gist: ") << ACTION_NAME << ": "
                    << bind(&std::exception::what, _1) << '\n') ;

   // Now, train the algorithm and save the results
   m_algo->train() ;
   m_algo->save_training_results() ;
}

//-----------------------------------------------------------------------

} // end of namespace encapsulating this file's definitions

/* So things look consistent in everyone's emacs... */
/* Local Variables: */
/* indent-tabs-mode: nil */
/* End: */
